/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <syslog.h>

#include <gdbus.h>

#include "connman.h"

#ifndef VCSID
#define VCSID "<not set>"
#endif

static volatile unsigned debug_enabled = 0;

/* A non-NULL sentinel pointer used as a value in masked_key_hash. */
static gpointer *sentinel_pointer = (gpointer)~0;
/*
 * A hash table, used as a set, to look up for keys whose value should be
 * masked out from the log
 */
static GHashTable *masked_key_hash = NULL;
/*
 * This string is printed to the log instead of the actual value of keys
 * being masked out
 */
static const char kMaskedValue[] = "[***]";

#define DBGTAG(tag)	{#tag, DBG_##tag}

/*
 * N.B. The following must be kept in sync with the enum
 *      of debug tag values in log.h.
 */
static struct _debugtag {
	const char *tagname;
	unsigned int mask;
} debugtags[] = {
	DBGTAG(AGENT),
	DBGTAG(CONNECTION),
	DBGTAG(DEVICE),
	DBGTAG(ELEMENT),
	DBGTAG(INET),
	DBGTAG(MANAGER),
	DBGTAG(NETWORK),
	DBGTAG(NOTIFIER),
	DBGTAG(PROFILE),
	DBGTAG(RESOLV),
	DBGTAG(RFKILL),
	DBGTAG(RTNL),
	DBGTAG(SECURITY),
	DBGTAG(SERVICE),
	DBGTAG(STORAGE),
	DBGTAG(TASK),
	DBGTAG(TEST),
	DBGTAG(UDEV),
	DBGTAG(METRICS),
	DBGTAG(CRYPTO),
	DBGTAG(PLUGIN),
	DBGTAG(BLUETOOTH),
	DBGTAG(DHCLIENT),
	DBGTAG(DNSPROXY),
	DBGTAG(ETHERNET),
	DBGTAG(HOSTROUTE),
	DBGTAG(PPPD),
	DBGTAG(VPN),
	DBGTAG(WIFI),
	DBGTAG(MODEM),
	DBGTAG(RESOLVFILES),
	DBGTAG(PORTAL),
	/* pseudo-tags */
	DBGTAG(ANY)
};

/**
 * connman_vinfo:
 * @format: format string
 * @ap: varags list of arguments
 *
 * Output general information
 */
void connman_vinfo(const char *format, va_list ap)
{
	vsyslog(LOG_INFO, format, ap);
}

/**
 * connman_info:
 * @format: format string
 * @Varargs: list of arguments
 *
 * Output general information
 */
void connman_info(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	connman_vinfo(format, ap);
	va_end(ap);
}


/**
 * connman_vwarn:
 * @format: format string
 * @ap: varargs list of arguments
 *
 * Output warning messages
 */
void connman_vwarn(const char *format, va_list ap)
{
	vsyslog(LOG_WARNING, format, ap);
}

/**
 * connman_warn:
 * @format: format string
 * @Varargs: list of arguments
 *
 * Output warning messages
 */
void connman_warn(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	connman_vwarn(format, ap);
	va_end(ap);
}

/**
 * connman_verror:
 * @format: format string
 * @ap: varargs list of arguments
 *
 * Output error messages
 */
void connman_verror(const char *format, va_list ap)
{
	vsyslog(LOG_ERR, format, ap);
}

/**
 * connman_error:
 * @format: format string
 * @varargs: list of arguments
 *
 * Output error messages
 */
void connman_error(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	connman_verror(format, ap);
	va_end(ap);
}

/**
 * connman_vdebug:
 * @mask: debug message mask
 * @format: format string
 * @ap: varargs list of arguments
 *
 * Output debug message
 *
 * The actual output of the debug message is controlled via a command line
 * switch. If not enabled, these messages will be ignored.
 */
void connman_vdebug(unsigned mask, const char *format, va_list ap)
{
	if ((debug_enabled & mask) != 0)
		vsyslog(LOG_DEBUG, format, ap);
}

/**
 * connman_debug:
 * @mask: debug message mask
 * @format: format string
 * @varargs: list of arguments
 *
 * Output debug message
 *
 * The actual output of the debug message is controlled via a command line
 * switch. If not enabled, these messages will be ignored.
 */
void connman_debug(unsigned mask, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	connman_vdebug(mask, format, ap);
	va_end(ap);
}

unsigned __connman_debug_setmask(unsigned debugmask)
{
	unsigned omask = debug_enabled;

	debug_enabled = debugmask;

	return omask;
}

unsigned __connman_debug_getmask(void)
{
	return debug_enabled;
}

unsigned __connman_debug_tags_to_mask(const char *tags, GError **error)
{
	gchar **taglist = g_strsplit(tags, "+", 0);
	gchar **tagp;
	gchar *tag;
	unsigned int debugmask = 0;
	int j;

	for (tagp = taglist; (tag = *tagp) != NULL; tagp++) {
		struct _debugtag *dtag = NULL;
		for (j = 0; j < G_N_ELEMENTS(debugtags); j++) {
			if (g_ascii_strcasecmp(tag, debugtags[j].tagname)
			    == 0) {
				dtag = &debugtags[j];
				debugmask |= dtag->mask;
				break;
			}
		}
		if (dtag == NULL) {
			g_set_error(error,
				    G_OPTION_ERROR,
				    G_OPTION_ERROR_BAD_VALUE,
				    "Unknown debug tag \"%s\"", tag);
			break;
		}
	}

	g_strfreev(taglist);
	return debugmask;
}

connman_bool_t connman_debug_enabled(unsigned debugmask)
{
	return (debug_enabled & debugmask) ? TRUE : FALSE;
}

gchar *__connman_debug_get_tag_list(unsigned int debugmask)
{
	int i;
	unsigned int val = debugmask;
	unsigned int numbits;
	int vecind;
	gchar **tagvec;
	gchar *taglist;
	gchar *cp;

	if (debugmask == 0)
		return g_strdup("");

	/*
	 * Use Kernighan's method for counting number of bits set. See
	 * http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
	 * The number of iteration is equal to the number of set bits.
	 */
	for (numbits = 0; val != 0; numbits++) {
		val &= val - 1;
	}

	tagvec = g_new(gchar *, numbits+1);
	if (tagvec == NULL)
		return NULL;

	for (i = 0, vecind = 0; i < G_N_ELEMENTS(debugtags); i++) {
		struct _debugtag *dtag = &debugtags[i];
		if ((debugmask & dtag->mask) == dtag->mask &&
		    dtag->mask != DBG_ANY)
			tagvec[vecind++] = (gchar *)dtag->tagname;
	}
	tagvec[vecind] = NULL;
	taglist = g_strjoinv("+", tagvec);
	for (cp = taglist; *cp != '\0'; ++cp)
		*cp = g_ascii_tolower(*cp);
	g_free(tagvec);
	return taglist;
}

gchar *__connman_debug_get_tags()
{
	return __connman_debug_get_tag_list(__connman_debug_getmask());
}

gchar *__connman_debug_get_all_tags()
{
	return __connman_debug_get_tag_list(DBG_ANY);
}

gboolean __connman_debug_set_tags(char *taglist)
{
	GError *error = NULL;
	unsigned int debugmask;

	debugmask = __connman_debug_tags_to_mask(taglist, &error);
	if (error != NULL)
		return FALSE;
	__connman_debug_setmask(debugmask);
	return TRUE;

}

int __connman_log_init(gboolean detach, unsigned debugmask)
{
	int option = LOG_NDELAY | LOG_PID;

	if (detach == FALSE)
		option |= LOG_PERROR;

	openlog("flimflamd", option, LOG_DAEMON);

	syslog(LOG_INFO, "vcsid %s", VCSID);

	__connman_debug_setmask(debugmask);

	masked_key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
						g_free, NULL);

	return 0;
}

void __connman_log_cleanup(void)
{
	g_hash_table_destroy(masked_key_hash);

	syslog(LOG_INFO, "Exit");

	closelog();
}

/**
 * connman_log_mask_value_of_key:
 * @key: key whose value to be masked out
 *
 * Mask out the value of the provided key in the log.
 */
void connman_log_mask_value_of_key(const char *key)
{
	g_hash_table_insert(masked_key_hash, g_strdup(key), sentinel_pointer);
}

/**
 * connman_log_get_masked_value:
 * @key: key associated with the value
 * @value: value to be printed to the log
 *
 * Return kMaskedValue if @key is in masked_key_hash, or @value otherwise.
 */
const char *connman_log_get_masked_value(const char *key, const char *value)
{
	if (g_hash_table_lookup(masked_key_hash, key) != NULL)
		return kMaskedValue;

	return value;
}
